//: C25:PaperScissorsRock.cpp
// From Thinking in C++, 2nd Edition
// Available at http://www.BruceEckel.com
// (c) Bruce Eckel 1999
// Copyright notice in Copyright.txt
// Demonstration of multiple dispatching
#include "../purge.h"
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <ctime>
using namespace std;

class Paper;
class Scissors;
class Rock;

enum Outcome { win, lose, draw };

ostream&
operator<<(ostream& os, const Outcome out)
{
    switch (out) {
    default:
    case win:
        return os << "win";
    case lose:
        return os << "lose";
    case draw:
        return os << "draw";
    }
}

class Item
{
public:
    virtual Outcome compete(const Item*) = 0;
    virtual Outcome eval(const Paper*) const = 0;
    virtual Outcome eval(const Scissors*) const= 0;
    virtual Outcome eval(const Rock*) const = 0;
    virtual ostream& print(ostream& os) const = 0;
    virtual ~Item() {}
    friend ostream&
    operator<<(ostream& os, const Item* it) {
        return it->print(os);
    }
};

class Paper : public Item
{
public:
    Outcome compete(const Item* it) {
        return it->eval(this);
    }
    Outcome eval(const Paper*) const {
        return draw;
    }
    Outcome eval(const Scissors*) const {
        return win;
    }
    Outcome eval(const Rock*) const {
        return lose;
    }
    ostream& print(ostream& os) const {
        return os << "Paper   ";
    }
};

class Scissors : public Item
{
public:
    Outcome compete(const Item* it) {
        return it->eval(this);
    }
    Outcome eval(const Paper*) const {
        return lose;
    }
    Outcome eval(const Scissors*) const {
        return draw;
    }
    Outcome eval(const Rock*) const {
        return win;
    }
    ostream& print(ostream& os) const {
        return os << "Scissors";
    }
};

class Rock : public Item
{
public:
    Outcome compete(const Item* it) {
        return it->eval(this);
    }
    Outcome eval(const Paper*) const {
        return win;
    }
    Outcome eval(const Scissors*) const {
        return lose;
    }
    Outcome eval(const Rock*) const {
        return draw;
    }
    ostream& print(ostream& os) const {
        return os << "Rock    ";
    }
};

struct ItemGen {
    ItemGen() {
        srand(time(0));
    }
    Item* operator()() {
        switch (rand() % 3) {
        default:
        case 0:
            return new Scissors();
        case 1:
            return new Paper();
        case 2:
            return new Rock();
        }
    }
};

struct Compete {
    Outcome operator()(Item* a, Item* b) {
        cout << a << "\t" << b << "\t";
        return a->compete(b);
    }
};

int main()
{
    const int sz = 20;
    vector<Item*> v(sz*2);
    generate(v.begin(), v.end(), ItemGen());
    transform(v.begin(), v.begin() + sz,
              v.begin() + sz,
              ostream_iterator<Outcome>(cout, "\n"),
              Compete());
    purge(v);
} ///:~
